//A layer control which provides for layer groupings.
//Author: Ishmael Smyrnow

L.Control.GroupedLayers = L.Control.extend({
    options: {
//        collapsed: true,
        position: 'bottomleft',
//        autoZIndex: true
    },
    initialize: function(baseLayers, groupedExclusiveOverlays, groupedOverlays, options) {
        L.Util.setOptions(this, options);

        this._layers = {};
        this._lastZIndex = 0;
        this._handlingClick = false;
        this._groupList = [];
        this._domGroups = [];

        this._groupedExclusiveOverlays = {};

        for (var i in baseLayers) {
            this._addLayer(baseLayers[i], i);
        }

//        var group = 0;
        for (i in groupedExclusiveOverlays) {
            this._addLayerGroup(i);
            for (var j in groupedExclusiveOverlays[i]) {
                this._addLayerToGroup(groupedExclusiveOverlays[i][j], j, i);
            }
//            group++;
        }

        for (i in groupedOverlays) {
            for (j in groupedOverlays[i]) {
                this._addLayer(groupedOverlays[i][j], j, i, true);
            }
        }
    },
    onAdd: function(map) {
        this._initLayout();
        this._update();
        map
                .on('layeradd', this._onLayerChange, this)
                .on('layerremove', this._onLayerChange, this);

        return this._container;
    },
    onRemove: function(map) {
        map
                .off('layeradd', this._onLayerChange)
                .off('layerremove', this._onLayerChange);
    },
    addBaseLayer: function(layer, name) {
        this._addLayer(layer, name);
        this._update();
        return this;
    },
    addOverlay: function(layer, name, group) {
        this._addLayer(layer, name, group, true);
        this._update();
        return this;
    },
    removeLayer: function(layer) {
        var id = L.Util.stamp(layer);
        delete this._layers[id];
        this._update();
        return this;
    },
    _initLayout: function() {
        var className = 'leaflet-control-layers',
                container = this._container = L.DomUtil.create('div', className);

        //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
        container.setAttribute('aria-haspopup', true);

        if (!L.Browser.touch) {
            L.DomEvent.disableClickPropagation(container);
            L.DomEvent.on(container, 'wheel', L.DomEvent.stopPropagation);
        } else {
            L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
        }

        var form = this._form = L.DomUtil.create('form', className + '-list');

        if (this.options.collapsed) {
            if (!L.Browser.android) {
                L.DomEvent
                        .on(container, 'mouseover', this._expand, this)
                        .on(container, 'mouseout', this._collapse, this);
            }
            var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
            link.href = '#';
            link.title = 'Layers';

            if (L.Browser.touch) {
                L.DomEvent
                        .on(link, 'click', L.DomEvent.stop)
                        .on(link, 'click', this._expand, this);
            }
            else {
                L.DomEvent.on(link, 'focus', this._expand, this);
            }

            this._map.on('click', this._collapse, this);
            // TODO keyboard accessibility
        } else {
            this._expand();
        }

        this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
        this._separator = L.DomUtil.create('div', className + '-separator', form);
        this._overlayExclusiveGroupsList = L.DomUtil.create('div', className + '-overlaygroups', form);
        this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);

        container.appendChild(form);
    },
    _addLayer: function(layer, name, group, overlay) {
        var id = L.Util.stamp(layer);

        this._layers[id] = {
            layer: layer,
            name: name,
            overlay: overlay
        };

        if (group) {
            var groupId = this._groupList.indexOf(group);

            if (groupId === -1) {
                groupId = this._groupList.push(group) - 1;
            }

            this._layers[id].group = {
                name: group,
                id: groupId
            };
        }

        if (this.options.autoZIndex && layer.setZIndex) {
            this._lastZIndex++;
            layer.setZIndex(this._lastZIndex);
        }
    },
    addOverlayGroup: function(overlayGroup) {
        var newGroupNum = Object.keys(this._groupedExclusiveOverlays).length;
        this._addLayerGroup(newGroupNum);
        for (layer in overlayGroup) {
            this._addLayerToGroup(overlayGroup[layer], layer, newGroupNum);
        }
        this._update();
    },
    removeOverlayGroup: function(overlayGroup) {
        var layersInRemoveGroup = Object.keys(overlayGroup).length;
        for (group in this._groupedExclusiveOverlays) {
            if (layersInRemoveGroup == Object.keys(this._groupedExclusiveOverlays[group]).length) {
                var groupFound = true;
                for (rLayer in overlayGroup) {
                    var layerFound = false;
                    for (pLayer in this._overlayGroups[group]) {
                        if (overlayGroup[rLayer] == this._groupedExclusiveOverlays[group][pLayer].layer) {
                            layerFound = true;
                        }
                    }
                    !layerFound || !groupFound ? groupFound = false : null
                }
                groupFound ? delete this._groupedExclusiveOverlays[group] : null
            }
        }
        this._update();
    },
    _addLayerGroup: function(group) {
        this._groupedExclusiveOverlays[group] = [];
    },
    _addLayerToGroup: function(layer, name, group) {
        var id = L.Util.stamp(layer);
        this._groupedExclusiveOverlays[group][id] = {
            layer: layer,
            name: name,
            group: group
        };
    },
    _update: function() {
        if (!this._container) {
            return;
        }

        this._baseLayersList.innerHTML = '';
        this._overlaysList.innerHTML = '';
        this._overlayExclusiveGroupsList.innerHTML = '';

        var baseLayersPresent = false,
                overlaysPresent = false,
                overlayExclusiveGroupsPresent = false;

        for (i in this._layers) {
            obj = this._layers[i];
            this._addItem(obj);
            overlaysPresent = overlaysPresent || obj.overlay;
            baseLayersPresent = baseLayersPresent || !obj.overlay;
        }

        this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';

        this._groupedExclusiveOverlays ? overlayExclusiveGroupsPresent = true : overlayExclusiveGroupsPresent = false;
        if (overlayExclusiveGroupsPresent) {
            for (i in this._groupedExclusiveOverlays) {
                this._addGroup(this._groupedExclusiveOverlays[i], i);
            }
        }
    },
    _onLayerChange: function(e) {
        var obj = this._layers[L.Util.stamp(e.layer)];

        if (!obj) {
            return;
        }

        if (!this._handlingClick) {
            this._update();
        }

        var type = obj.overlay ?
                (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
                (e.type === 'layeradd' ? 'baselayerchange' : null);

        if (type) {
            this._map.fire(type, obj);
        }
    },
    // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
    _createRadioElement: function(name, checked) {

        var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
        if (checked) {
            radioHtml += ' checked="checked"';
        }
        radioHtml += '/>';

        var radioFragment = document.createElement('div');
        radioFragment.innerHTML = radioHtml;

        return radioFragment.firstChild;
    },
    _addItem: function(obj) {
        var label = document.createElement('label'),
                input,
                checked = this._map.hasLayer(obj.layer);

        if (obj.overlay) {
            input = document.createElement('input');
            input.type = 'checkbox';
            input.className = 'leaflet-control-layers-selector';
            input.defaultChecked = checked;
        } else {
            input = this._createRadioElement('leaflet-base-layers', checked);
        }

        input.layerId = L.Util.stamp(obj.layer);

        L.DomEvent.on(input, 'click', this._onInputClick, this);

        var name = document.createElement('span');
        name.innerHTML = ' ' + obj.name;

        label.appendChild(input);
        label.appendChild(name);

        if (obj.overlay) {
            container = this._overlaysList;

            var groupContainer = this._domGroups[obj.group.id];

            // Create the group container if it doesn't exist
            if (!groupContainer) {
                groupContainer = document.createElement('div');
                groupContainer.className = 'leaflet-control-layers-group';
                groupContainer.id = 'leaflet-control-layers-group-' + obj.group.id;

                var groupLabel = document.createElement('span');
                groupLabel.className = 'leaflet-control-layers-group-name';
                groupLabel.innerHTML = obj.group.name;

                groupContainer.appendChild(groupLabel);
                container.appendChild(groupContainer);

                this._domGroups[obj.group.id] = groupContainer;
            }

            container = groupContainer;
        } else {
            container = this._baseLayersList;
        }

        container.appendChild(label);

        return label;
    },
    _addGroup: function(group, groupName) {

        var separator = L.DomUtil.create('div', 'leaflet-control-layers-group-separator', this._form);

        var container = this._overlayExclusiveGroupsList;
        container.appendChild(separator);
        var groupLabel = document.createElement('span');
        groupLabel.className = 'leaflet-control-layers-group-name';
        groupLabel.innerHTML = groupName;

        container.appendChild(groupLabel);


        for (var i in group) {
            this._addGroupItem(group[i]);
        }

    },
    _addGroupItem: function(obj) {
        var label = document.createElement('label');
        var input = document.createElement('input');

        input.name = 'leaflet-overlay-group-' + obj.group;

        input.type = 'radio';
        input.checked = this._map.hasLayer(obj.layer);
        input.layerId = L.Util.stamp(obj.layer);

        L.DomEvent.addListener(input, 'click', this._onInputClick, this);

        var name = document.createTextNode(' ' + obj.name);

        label.appendChild(input);
        label.appendChild(name);

        var container = this._overlayExclusiveGroupsList;
        container.appendChild(label);
    },
    _onInputClick: function() {
        var i, input, obj;
//                inputs = this._form.getElementsByTagName('input'),
//                inputsLen = inputs.length;
        var baseLayers = this._form.getElementsByClassName('leaflet-control-layers-base')[0].getElementsByTagName('input');
        var overlays = this._form.getElementsByClassName('leaflet-control-layers-overlays')[0].getElementsByTagName('input');
        var overlayGroups = this._form.getElementsByClassName('leaflet-control-layers-overlaygroups');

        this._handlingClick = true;

        for (i = 0; i < baseLayers.length; i++) {
            input = baseLayers[i];

            obj = this._layers[input.layerId];

            if (input.checked && !this._map.hasLayer(obj.layer)) {
                this._map.addLayer(obj.layer);

            } else if (!input.checked && this._map.hasLayer(obj.layer)) {
                this._map.removeLayer(obj.layer);
            }
        }
        var groupNum = 0;
        for (group in this._groupedExclusiveOverlays) {
            var layerNum = 0;
            var overlayGroup = overlayGroups[groupNum].getElementsByTagName('input');
            for (layer in this._groupedExclusiveOverlays[group]) {
                input = overlayGroup[layerNum];
                obj = this._groupedExclusiveOverlays[group][layer];

                if (input.checked) {
                    
                    this._map.removeLayer(obj.layer);
                    this._map.addLayer(obj.layer, obj.overlay);
                    this._map.fire('exclusivelayerchange', {layer: obj.layer});
                } else {
                    this._map.removeLayer(obj.layer);
                }
                layerNum++;
            }
            groupNum++;
        }

        for (i = 0; i < overlays.length; i++) {
            input = overlays[i];
            obj = this._layers[input.layerId];

            if (input.checked) {
                this._map.removeLayer(obj.layer);
                this._map.addLayer(obj.layer, obj.overlay);
            } else {
                this._map.removeLayer(obj.layer);
            }
        }

        this._handlingClick = false;
    },
    _expand: function() {
        L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
    },
    _collapse: function() {
        this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
    }
});

L.control.groupedLayers = function(baseLayers, groupedOverlays, options) {
    return new L.Control.GroupedLayers(baseLayers, groupedOverlays, options);
};